#include "p2sp_call_agent.hpp"
#include "p2pms_log.hpp"
#include "p2pms_util.hpp"
#include "capturer_track_source.hpp"

#include "mediasoupclient.hpp"

#include "rtc_base/logging.h"
#include "rtc_base/ssl_adapter.h"
#include "api/create_peerconnection_factory.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/video_codecs/video_decoder_factory_template.h"
#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"

namespace p2pms
{

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
P2SPCallAgent::P2SPCallAgent(CallRole role, const std::string& user_id, IPcFactoryPtr factory, 
	ICallHandler* handler, rtc::Thread* thread, std::shared_ptr<IMsgDispatcher> dispatcher)
	: BaseCallAgent(CallMode::CALL_MODE_P2SP, role, user_id)
	, m_sfu_client(user_id, this, factory, thread, dispatcher)
	, m_call_handler(handler)
	, m_sdk_thread(thread)
	, m_dispatcher(dispatcher)
{
	assert(m_call_handler);
	assert(m_sdk_thread);
	assert(m_dispatcher);

	m_dispatcher->Register(CALL_MAKE_RES,
		[this](const Message& msg, CBType cbt) { OnCallMakeRes(msg, cbt); });
	m_dispatcher->Register(CALL_END_NOTIFY,
		[this](const Message& msg, CBType cbt) { OnCallEndNotify(msg, cbt); });
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
P2SPCallAgent::~P2SPCallAgent()
{
	m_dispatcher->Unregister(CALL_MAKE_RES);
	m_dispatcher->Unregister(CALL_END_NOTIFY);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::Init()
{
	return m_sfu_client.Init();
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::MakeCall(const std::string& callee_id)
{
	if (callee_id.empty()) {
		LOG_ERR("Callee ID is empty");
		return ERR_INVALID_PARAM;
	}

	if (m_call_role != CallRole::Caller) {
		LOG_ERR("Call role is not Caller, cannot initiate call");
		return ERR_FAILED;
	}

	if (!m_dispatcher->SendMsg(MakeCallMakeReqMsg("p2sp", GetUserId(), callee_id), CALL_MAKE_RES)) {
		LOG_ERR("Failed to send call request message");
		return ERR_FAILED;
	}

	m_callee_id = callee_id;

	CallStateChanged(CallState::Calling);

	return ERR_OK;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::IncomingCall(const CallMakeNotifyData& notify_data)
{
	if (notify_data.call_type != "p2sp") {
		LOG_ERR("SFU call agent received non-SFU call type: %s", notify_data.call_type.c_str());
		return ERR_INVALID_PARAM;
	}

	if (notify_data.callee_id != m_callee_id) {
		LOG_ERR("Incoming call callee ID does not match agent's callee ID: %s != %s",
			notify_data.callee_id.c_str(), m_callee_id.c_str());
		return ERR_INVALID_PARAM;
	}

	if (notify_data.call_id.empty()) {
		LOG_ERR("Call ID is empty in call notification");
		return ERR_INVALID_PARAM;
	}
	
	m_call_id = notify_data.call_id; // For SFU call, call ID is the room_id

	CallStateChanged(CallState::Calling);

	return m_sfu_client.JoinRoom(notify_data.call_id);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::TerminateCall()
{
	if (m_call_state == CallState::Idle ||
		m_call_state == CallState::Terminating ||
		m_call_state == CallState::Terminated) {
		LOG_INF("Invalid call state:{} to terminate call, no action taken", m_call_state);
		return;
	}

	CallStateChanged(CallState::Terminating);

	if (m_call_state == CallState::Connected) {
		if (m_sfu_client.LeaveRoom() != ERR_OK) {
			LOG_ERR("Leave room failed");
		}

		if (!DoTerminateCall()) {
			LOG_ERR("Do terminate call failed");
		}
	} else {
		LOG_INF("Call is not connected, skipping call end message");
	}

	CallStateChanged(CallState::Terminated);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::StartPublish(const MediaInfo& media)
{
	if (m_call_state != CallState::Connected) {
		LOG_ERR("Cannot publish media, call not connected");
		return ERR_FAILED;
	}

	if (!ValidateMediaInfo(media)) {
		LOG_ERR("Invalid media info for publishing");
		return ERR_FAILED;
	}

	return m_sfu_client.StartPublish(media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::StopPublish(const MediaInfo& media)
{
	//if (m_call_state != CallState::Connected) {
	//	LOG_ERR("Cannot stop publishing media, call not connected");
	//	return false;
	//}

	return m_sfu_client.StopPublish(media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::StartSubscribe(const std::string& user_id, const MediaInfo& media)
{
	if (m_call_state != CallState::Connected) {
		LOG_ERR("Cannot subscribe to media, call not connected");
		return ERR_FAILED;
	}

	if (!ValidateMediaInfo(media)) {
		LOG_ERR("Invalid media info for subscription");
		return ERR_FAILED;
	}

	return m_sfu_client.StartSubscribe(user_id, media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
ErrCode P2SPCallAgent::StopSubscribe(const std::string& user_id, const MediaInfo& media)
{
	if (m_call_state != CallState::Connected) {
		LOG_ERR("Cannot stop subscribing to media, call not connected");
		return ERR_FAILED;
	}

	return m_sfu_client.StopSubscribe(user_id, media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnTimer()
{
	m_sfu_client.OnTimer();
}

////////////////////////////////////////////////////////////////////////////////////////////////////

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool P2SPCallAgent::DoTerminateCall()
{
	auto result = m_dispatcher->SendMsgSync(
		MakeCallEndReqMsg(m_call_id, GetUserId()), CALL_END_RES);

	if (result.cbt == CBType::Timeout) {
		LOG_ERR("Wait end call response timeout");
		return false;
	} 
	else if (result.cbt == CBType::Error) {
		LOG_ERR("Failed to send call end request");
		return false;
	}

	LOG_INF("Call end request sent successfully");

	return true;
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::CallStateChanged(CallState state)
{
	if (m_call_state != state) {
		m_call_state = state;
		if (m_call_handler) {
			m_call_handler->OnCallStateChanged(m_call_id, state);
		}
	}
	else {
		LOG_WRN("Call state unchanged: {}", ToString(state));
	}
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool P2SPCallAgent::ParseCallMakeRes(const Message& msg, CallMakeResData& data)
{
	if (!msg.data.is_object()) {
		LOG_ERR("Call response data is not an object");
		return false;
	}

	ResMsgData res_data;
	try {
		res_data = msg.data.get<ResMsgData>();
	}
	catch (const std::exception& e) {
		LOG_ERR("Failed to parse response data: {}", e.what());
		return false;
	}

	if (res_data.code != 0) {
		LOG_ERR("Call response error: code={}, msg={}", res_data.code, res_data.msg);
		return false;
	}

	try {
		data = res_data.data.get<CallMakeResData>();
	}
	catch (const std::exception& e) {
		LOG_ERR("Failed to parse call response data: {}", e.what());
		return false;
	}

	if (data.call_id.empty()) {
		LOG_ERR("Call response missing call ID");
		return false;
	}

	return true;
}

//--------------------------------------------------------------------------------------------------
// Call Protocol Message Handlers
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnCallMakeRes(const Message& msg, CBType cbt)
{
	assert(m_sdk_thread);
	m_sdk_thread->PostTask([this, msg, cbt]() {
		if (cbt == CBType::Timeout) {
			CallStateChanged(CallState::Terminated);
			return;
		}

		CallMakeResData call_res_data;
		if (!ParseCallMakeRes(msg, call_res_data)) {
			CallStateChanged(CallState::Terminated);
			return;
		}

		m_call_id = call_res_data.call_id; // For SFU, room_id is the same as call_id

		m_sfu_client.JoinRoom(m_call_id);
	});
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnCallEndNotify(const Message& msg, CBType cbt)
{
	LOG_INF("Received call end notify");
	
	assert(m_sdk_thread);
	m_sdk_thread->PostTask([this, msg]() {
		CallEndNotifyData notify_data;
		if (!ParseRequest(msg, notify_data)) {
			return;
		}

		if (notify_data.call_id != m_call_id) {
			LOG_ERR("Call end notify call ID does not match current call ID: {} != {}",
				notify_data.call_id, m_call_id);
			return;
		}

		m_dispatcher->SendMsg(MakeCallEndAckMsg(
			notify_data.call_id, 0, "ok", notify_data.call_id, notify_data.user_id));

		CallStateChanged(CallState::Terminated);
	});
}

////////////////////////////////////////////////////////////////////////////////////////////////////

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnJoinRoomResult(bool result)
{
	result ? CallStateChanged(CallState::Connected) : CallStateChanged(CallState::Terminated);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStartPublishResult(const MediaInfo& media, bool result)
{
	m_call_handler->OnStartPublishResult(media, result);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStopPublishResult(const MediaInfo& media, bool result)
{
	m_call_handler->OnStopPublishResult(media, result);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStartPublishNotify(const std::string& user_id, const MediaInfo& media)
{
	m_call_handler->OnStartPublish(user_id, media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStopPublishNotify(const std::string& user_id, const MediaInfo& media)
{
	m_call_handler->OnStopPublish(user_id, media);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStartSubscribeResult(const std::string& user_id, const MediaInfo& media,
	bool result)
{
	m_call_handler->OnStartSubscribeResult(user_id, media, result);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnStopSubscribeResult(const std::string& user_id, const MediaInfo& media,
	bool result)
{
	m_call_handler->OnStopSubscribeResult(user_id, media, result);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnAddTrack(const std::string& user_id, const MediaInfo& media,
	webrtc::MediaStreamTrackInterface* track)
{
	m_call_handler->OnAddTrack(user_id, media, track);
}

//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void P2SPCallAgent::OnRemoveTrack(const std::string& user_id, const MediaInfo& media,
	webrtc::MediaStreamTrackInterface* track)
{
	m_call_handler->OnRemoveTrack(user_id, media, track);
}

}
